﻿// Inner Fire 游戏引擎库
// DirectX图型主类
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 这个类用于读取XML，并将其转化为交互者友好的类。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-15

#include "dx_graphics.h"
#include <dx_scene/dx_scene.h>

using namespace DirectX;

namespace ifire {

DxGraphics::DxGraphics() {}

DxGraphics::~DxGraphics() {
  if (device_ != nullptr) {
    FlushCommandQueue();
  }
}

void DxGraphics::FlushCommandQueue() {
  // 步进fence点，使当前的命令节点为此fence.
  current_fence_++;
  // 向命令队列中添加一条指令，设置一个新的栅栏点。在GPU时间线上，
  // 新的栅栏点要等到GPU完成处理所有在此Signal()之前的命令后才会设置。
  HASSERT(command_queue_->Signal(fence_.Get(), current_fence_));

  // 等待GPU执行到此fence点
  if (fence_->GetCompletedValue() < current_fence_) {
    // 创建一个匿名事件
    HANDLE handle = CreateEventEx(nullptr, NULL, false, EVENT_ALL_ACCESS);
    // 设置当GPU完成到fence点的时候执行事件
    HASSERT(fence_->SetEventOnCompletion(current_fence_, handle));
    // 等待事件触发
    WaitForSingleObject(handle, INFINITE);
    // 关闭事件
    CloseHandle(handle);
  }
}

ID3D12Resource* DxGraphics::GetBackBuffer() const {
  return swap_chain_buffer_[current_back_buffer_].Get();
}

D3D12_CPU_DESCRIPTOR_HANDLE DxGraphics::GetBackBufferView() const {
  return CD3DX12_CPU_DESCRIPTOR_HANDLE(
      rtv_heap_->GetCPUDescriptorHandleForHeapStart(), // 堆中的第一个句柄
      current_back_buffer_, // 偏移至后台缓冲区描述符句柄的索引
      rtv_descriptor_size_  // 描述符大小（就用在这里）
  );
}

D3D12_CPU_DESCRIPTOR_HANDLE DxGraphics::GetDepthStencilView() const {
  // 为什么DSV不需要偏移呢？
  return dsv_heap_->GetCPUDescriptorHandleForHeapStart();
}

ID3D12Resource* DxGraphics::CurrentBackBuffer() const {
  return swap_chain_buffer_[current_back_buffer_].Get();
}

D3D12_CPU_DESCRIPTOR_HANDLE DxGraphics::CurrentBackBufferView() const {
  return CD3DX12_CPU_DESCRIPTOR_HANDLE(
      rtv_heap_->GetCPUDescriptorHandleForHeapStart(), // 堆中的第一个句柄
      current_back_buffer_, // 偏移至后台缓冲区描述符句柄的索引
      rtv_descriptor_size_  // 描述符大小（就用在这里）
  );
}

D3D12_CPU_DESCRIPTOR_HANDLE DxGraphics::DepthStencilView() const {
  // 为什么DSV不需要偏移呢？
  return dsv_heap_->GetCPUDescriptorHandleForHeapStart();
}

float DxGraphics::GetAspectRatio() const {
  return static_cast<float>(client_width_) / client_height_;
}

bool DxGraphics::Get4xMsaaState() const { return msaa_enable_; }

void DxGraphics::Set4xMsaaState(bool value) {
  if (msaa_enable_ != value) {
    msaa_enable_ = value;

    // Recreate the swapchain and buffers with new multisample settings.
    CreateSwapChain();
    OnResize();
  }
}

void DxGraphics::SetClientSize(int width, int height) {
  client_width_ = width;
  client_height_ = height;
}

void DxGraphics::SetScene(DxScene* scene) {
  scene_ = scene;
  scene_->SetGraphics(this);
}

ComPtr<ID3D12Resource> DxGraphics::CreateDefaultBuffer(
    const void* data, UINT64 data_size, ComPtr<ID3D12Resource>& upload_buffer) {
  // 缓冲区指针
  ComPtr<ID3D12Resource> buffer;

  // 创建实际的默认缓冲区资源
  CD3DX12_HEAP_PROPERTIES heap_props(D3D12_HEAP_TYPE_DEFAULT);
  auto buffer_desc = CD3DX12_RESOURCE_DESC::Buffer(data_size);
  HASSERT(device_->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE,
      &buffer_desc, D3D12_RESOURCE_STATE_COMMON, nullptr,
      IID_PPV_ARGS(buffer.GetAddressOf())));

  // 为了将CPU端内存的数据复制到默认缓冲区，需要一个处于中介位置的上传堆
  CD3DX12_HEAP_PROPERTIES upload_props(D3D12_HEAP_TYPE_UPLOAD);
  auto upload_buffer_desc = CD3DX12_RESOURCE_DESC::Buffer(data_size);
  HASSERT(device_->CreateCommittedResource(&upload_props, D3D12_HEAP_FLAG_NONE,
      &upload_buffer_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
      IID_PPV_ARGS(upload_buffer.GetAddressOf())));

  // 描述要复制到默认缓冲区的数据
  D3D12_SUBRESOURCE_DATA subresource_data = {};
  subresource_data.pData = data;
  subresource_data.RowPitch = data_size;
  subresource_data.SlicePitch = subresource_data.RowPitch;

  // 将数据复制到默认缓冲区的流程如下：
  // 1：UpdateSubresources将数据从CPU内存复制到中介的上传堆
  // 2：通过CopySubresourceRegion把上传堆的数据复制到buffer中。

  // 使用资源屏障将buffer从COMMON(常规)设置为COPY_DEST(复制目标)
  auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(buffer.Get(),
      D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
  command_list_->ResourceBarrier(1, &barrier);
  // 上传资源到Uploader。
  // 这里我有一点以前没搞明白的。按理来说，这条执行之后，Uploader中就已经存在了数据。
  // 原数据就可以删除了，只是Uploader这个中介不能删除而已。因为教程中用了std::vector，
  // 在函数进行完就会被释放的。
  UpdateSubresources<1>(command_list_.Get(), buffer.Get(), upload_buffer.Get(),
      0, 0, 1, &subresource_data);
  // 使用资源屏障将buffer从COPY_DEST(复制目标)转为GENERIC_READ（可读用）
  auto barrier2 = CD3DX12_RESOURCE_BARRIER::Transition(buffer.Get(),
      D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ);
  command_list_->ResourceBarrier(1, &barrier2);

  // 注意，在调用上述函数之后，必须保存load_buffer依然存在，而不能对它立刻进行销毁。
  // 因为命令列表中的复制操作可能还没有执行。当调用者知道复制已完成才能释放upload_buffer。

  return buffer;
}


} // namespace ifire